#!/usr/bin/env python

class PerformanceControl:
  '''
  The PerformanceControl controls performance for selected counter.
  '''  
  def __init__ (self, monitorThread, counter, max = 80, min = 10, insensitiveRange = 4):
    '''
    monitorThread: object
         MonitorThread class object
    counter: string
         name of controlled counter
    max: int
         maximum counter's value 
    min: int
         minimum counter's value
    insensitiveRange: int
         insensitive range of counter's values
    '''
    import threading
    self.monitorThread = monitorThread
    self.counter = counter
    
    self.lock = threading.RLock()
    self.event = threading.Event()
    self.maxFunc = None
    self.minFunc = None
    self.normFunc = None
    self.lastFunc = None
    self.max = max
    self.min = min
    self.insensitiveRange = insensitiveRange
    self.numMaxCalls = 0
    self.numMinCalls = 0
    self.numNormCalls = 0
    self.limitMaxCalls = None
    self.limitNormCalls = None
    self.limitMinCalls = None

  def accept(self, visitor):
    '''
    Sets visitor's state functions to class state functions (maxFunc, minFunc and normFunc).
    visitor: object
         Visitor class object
    '''
    self.lock.acquire()
    self.maxFunc = visitor.maxFunc
    self.minFunc = visitor.minFunc
    self.normFunc = visitor.normFunc
    self.lastFunc = None
    self.lock.release()
    
  def callbackExecuteVisitor (self, dataProcess, dataThreads, procQuery, threadsQuery, prevProcData, prevThreadsData):
    '''
    Executes necessary state function (maxFunc, minFunc or normFunc) for counter's value.
    dataProcess: numpy array
         data array (dtype = typeProc)
    dataThreads: dictionary
         threads data dictionary consists of thread data numpy arrays (dtype = typeThread)
    procQuery: list
         list of process names of counters
    threadsQuery: list
         list of thread names of counters
    prevProcData: numpy array
         data array (dtype = typeProc) with length = 1
    prevThreadsData: dictionary
         threads data dictionary consists of thread data numpy arrays (dtype = typeThread) with length = 1  
    '''    
    import pyrfcon
    self.lock.acquire()
    if (self.maxFunc != None) and (self.minFunc != None) and (self.normFunc != None):
      dataProcess, dataThreads = pyrfcon.parseQuery (dataProcess, dataThreads, procQuery, threadsQuery, prevProcData, prevThreadsData)
      print dataProcess [procQuery[0]][-1]
      value = dataProcess [procQuery[0]][-1]
      if  value < self.min - self.insensitiveRange/2:
        if (self.limitMinCalls == None) or (self.numMinCalls < self.limitMinCalls):   
          self.numMinCalls = self.numMinCalls + self.minFunc()
        self.lastFunc = self.minFunc
        
      elif (value > self.min - self.insensitiveRange/2) and (value < self.min + self.insensitiveRange/2):
          if (self.lastFunc == None) or (self.lastFunc == self.maxFunc):
              if value < self.min:
                  self.lastFunc = self.minFunc
              else:
                  self.lastFunc = self.normFunc
          if self.lastFunc == self.minFunc:
              if (self.limitMinCalls == None) or (self.numMinCalls < self.limitMinCalls):
                self.numMinCalls = self.numMinCalls + self.lastFunc()
          else:
              if (self.limitNormCalls == None) or (self.numNormCalls < self.limitNormCalls):
                self.numNormCalls = self.numNormCalls + self.lastFunc()
            
      elif (value > self.min + self.insensitiveRange/2) and (value < self.max - self.insensitiveRange/2):
          if (self.limitNormCalls == None) or (self.numNormCalls < self.limitNormCalls):
            self.numNormCalls = self.numNormCalls + self.normFunc()
          self.lastFunc = self.normFunc
        
      elif (value > self.max - self.insensitiveRange/2) and (value < self.max + self.insensitiveRange/2):
          if (self.lastFunc == None) or (self.lastFunc == self.minFunc):
              if value > self.max:
                  self.lastFunc = self.maxFunc
              else:
                  self.lastFunc = self.normFunc
          if self.lastFunc == self.maxFunc:
              if (self.limitMaxCalls == None) or (self.numMaxCalls < self.limitMaxCalls):
                self.numMaxCalls = self.numMaxCalls + self.lastFunc()
          else:
              if (self.limitNormCalls == None) or (self.numNormCalls < self.limitNormCalls):
                self.numNormCalls = self.numNormCalls + self.lastFunc()
        
      elif value > self.max + self.insensitiveRange/2:
          if (self.limitMaxCalls == None) or (self.numMaxCalls < self.limitMaxCalls):
            self.numMaxCalls = self.numMaxCalls + self.maxFunc()
          self.lastFunc = self.maxFunc
    
      if self.limitMaxCalls == self.numMaxCalls:
        self.event.set()
      if self.limitNormCalls == self.numNormCalls:
        self.event.set()  
      if self.limitMinCalls == self.numMinCalls:
        self.event.set() 

    self.lock.release()
    
  def start (self):
    '''Starts to control performance.'''
    self.monitorThread.start()
    self.monitorThread.setCallback (self.callbackExecuteVisitor, [self.counter]) 
  
  def stop(self):
    '''Stops to control performance.'''
    self.monitorThread.clearCallback()
    self.monitorThread.setProcess(False)
    self.monitorThread.join()
    
  def waitReachLimit(self, limitMaxCalls = None, limitNormCalls = None, limitMinCalls = None):
    '''
    numMaxCalls, numNormCalls, numMinCalls = waitReachLimit(limitMaxCalls, limitNormCalls, limitMinCalls)
    Sets limits for calls of state functions (maxFunc, minFunc or normFunc) and waits for reach one of limits.
    limitMaxCalls: int
         limit for maxFunc calls, by default sets no limit for calls (limitMaxCalls = None)
    limitNormCalls: int
         limit for normFunc calls, by default sets no limit for calls (limitNormCalls = None)
    limitMinCalls:  int
         limit for minFunc calls, by default sets no limit for calls (limitMinCalls = None)
    numMaxCalls: int
         number of maxFunc calls 
    numNormCalls: int
         number of normFunc calls    
    numMinCalls:  int
         number of minFunc calls
    '''
    self.lock.acquire()
    self.event.clear()
    self.limitMaxCalls = limitMaxCalls
    self.limitNormCalls = limitNormCalls
    self.limitMinCalls = limitMinCalls
    self.numMaxCalls = 0
    self.numMinCalls = 0
    self.numNormCalls = 0
    self.lock.release()

    self.event.wait()
    
    self.lock.acquire()
    numMaxCalls = self.numMaxCalls
    numNormCalls = self.numNormCalls
    numMinCalls = self.numMinCalls
    self.lock.release()
    
    return numMaxCalls, numNormCalls, numMinCalls
